package com.study.examples.common.aspect;

import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.util.*;

/**
 * 访问日志过滤器
 *
 * @author wtang
 */
@Slf4j
@Component
//@WebFilter(urlPatterns = "/live/**")
public class AccessLogFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//        if (!config.isApilogSwitch()) {
//            filterChain.doFilter(servletRequest, servletResponse);
//            return;
//        }

        long startTime = System.currentTimeMillis();

        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) servletResponse);
        filterChain.doFilter(requestWrapper, responseWrapper);

        byte[] reqBody = requestWrapper.getBodyByteArray();
        byte[] respBody = responseWrapper.toByteArray();
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.getOutputStream().write(respBody);

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String path = request.getRequestURI();
        if (path.contains("swagger-")
                || path.contains("api-")
                || path.contains("/webjars")
                || path.endsWith("doc.html")) {
            return;
        }

        StringBuilder out = new StringBuilder();
        // method uri
        out.append("\n---------------------\n")
                .append(request.getMethod())
                .append(' ')
                .append(request.getRequestURI());
        if (!Strings.isNullOrEmpty(request.getQueryString())) {
            out.append('?').append(request.getQueryString());
        }
        out.append('\n');

        // request time
        out.append("Request time: ")
                .append(new Date(startTime))
                .append(" Spent: ")
                .append(System.currentTimeMillis() - startTime)
                .append("ms\n");

        // request headers
        out.append("Request headers:\n");
        buildHeaders(out, getRequestHeaders(request));

        // request body
        if (reqBody != null && reqBody.length > 0) {
            String content;
            if (contentTypeIsFile(request.getContentType())) {
                content = "[file]";
            } else {
                content = new String(reqBody);
            }
            out.append("Request body:\n")
                    .append(content)
                    .append("\n");
        }

        // response status
        out.append("Response status: ")
                .append(response.getStatus())
                .append('\n');

        // response headers
        out.append("Response headers:\n");
        buildHeaders(out, getResponseHeaders(response));

        // response body
        String content;
        if (contentTypeIsFile(response.getContentType())) {
            content = "[file]";
        } else {
            content = new String(respBody);
        }
        out.append("Response body:\n")
                .append(content)
                .append("\n");
        log.info(out.toString());
    }

    private static Map<String, Collection<String>> getRequestHeaders(HttpServletRequest request) {
        Map<String, Collection<String>> headers = new LinkedHashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            headers.put(name, Collections.list(request.getHeaders(name)));
        }
        return headers;
    }

    private static boolean contentTypeIsFile(String contentType) {
        if (contentType == null) {
            return false;
        }
        return contentType.contains("multipart") || contentType.contains("image");
    }

    private static Map<String, Collection<String>> getResponseHeaders(HttpServletResponse response) {
        Map<String, Collection<String>> headers = new LinkedHashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        for (String name : headerNames) {
            headers.put(name, response.getHeaders(name));
        }
        return headers;
    }

    private static void buildHeaders(StringBuilder out, Map<String, Collection<String>> headers) {
        headers.forEach((name, values) -> {
            out.append("\t").append(name).append(" : ");
            boolean first = true;
            for (String value : values) {
                if (first) {
                    first = false;
                } else {
                    out.append(" , ");
                }
                out.append(value);
            }
            out.append('\n');
        });
    }

    @Override
    public void destroy() {

    }

    static class RequestWrapper extends HttpServletRequestWrapper {
        private final byte[] body;
        private final ByteArrayInputStream buf;
        private final ServletInputStream in = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() {
                return buf.read();
            }
        };
        private final BufferedReader bufferedReader;

        public RequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            //noinspection UnstableApiUsage
            body = ByteStreams.toByteArray(request.getInputStream());
            buf = new ByteArrayInputStream(body);
            bufferedReader = new BufferedReader(new InputStreamReader(buf));
        }

        public byte[] getBodyByteArray() {
            return body;
        }

        @Override
        public ServletInputStream getInputStream() {
            return in;
        }

        @Override
        public BufferedReader getReader() {
            return bufferedReader;
        }
    }

    static class ResponseWrapper extends HttpServletResponseWrapper {
        private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
        private final ServletOutputStream out = new ServletOutputStream() {
            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {
            }

            @Override
            public void write(int b) {
                buf.write(b);
            }
        };
        private final PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(buf));

        public ResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public PrintWriter getWriter() {
            return printWriter;
        }

        @Override
        public ServletOutputStream getOutputStream() {
            return out;
        }

        @Override
        public void flushBuffer() throws IOException {
            out.flush();
        }

        @Override
        public void reset() {
            buf.reset();
        }

        public byte[] toByteArray() {
            return buf.toByteArray();
        }
    }
}
